package com.itdct.onflow.core.exception

import com.alibaba.fastjson2.toJSONString
import com.itdct.onflow.core.config.OnflowConfig
import com.itdct.onflow.core.constant.CommonString
import com.itdct.onflow.core.constant.ReturnCode
import com.itdct.onflow.core.context.BaseContext
import com.itdct.onflow.core.context.ContextHolder
import com.itdct.onflow.core.dto.BaseLoginDTO
import com.itdct.onflow.core.extend.logError
import com.itdct.onflow.core.vo.RespVo
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.converter.HttpMessageNotReadableException
import org.springframework.jdbc.BadSqlGrammarException
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.servlet.resource.NoResourceFoundException

/**
 * @author Zhouwx
 * @date 2024/5/30 19:09:23
 * @version 1.0
 * @description
 */
open class BaseHandleException(
    val onflowConfig: OnflowConfig
) {

    open fun handle(e: Exception, response: HttpServletResponse): RespVo<*> {
        val message = e.message
        when (e) {
            is CommonException -> {
                return handleCommonException(e, response)
            }

            is MethodArgumentNotValidException -> {
                return handleMethodArgumentValidException(e)
            }

            is BadSqlGrammarException -> {
                return handleSqlGrammarException(e)
            }

            is NoResourceFoundException -> {
                return handleNoResourceFoundException(e)
            }

            is HttpMessageNotReadableException -> {
                return handleHttpMessageNotReadableException(e)
            }

            else -> {
                // INFO: Zhouwx: 2024/4/23 其余类型的错误
                if (!message.isNullOrBlank()) {
                    if (message.contains("Row was updated or deleted by another transaction")) {
                        return handleMultiChangeException(e)

                    } else if (message.contains("Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry")) {
                        return handleDuplicateException(e)
                    } else {
                        val otherExceptionRespVo = handleOtherException(e)
                        if (otherExceptionRespVo != null) {
                            return otherExceptionRespVo
                        } else {
                            return handleUnknownException(e)
                        }
                    }
                } else {
                    return handleUnknownException(e)
                }
            }
        }
    }

    open fun handleOtherException(e: Exception): RespVo<*>? {
        return null
    }

    private fun handleHttpMessageNotReadableException(e: HttpMessageNotReadableException): RespVo<*> {
        logError(CommonString.JSON_INPUT_DATA_ERROR + getCommonErrorTrace(e))
        return fail(ReturnCode.REQUEST_DATA_ERROR, CommonString.JSON_INPUT_DATA_ERROR)
    }

    open fun handleUnknownException(e: Exception): RespVo<*> {
        logError(CommonString.SYS_UNKNOWN_ERROR, e)
        return fail(ReturnCode.UNCAUGHT_SYSTEM_ERROR, CommonString.SYS_UNKNOWN_ERROR)
    }

    open fun handleDuplicateException(e: Exception): RespVo<*> {
        val it = e.message!!
        // INFO: Zhouwx: 2024/5/30 找到重复值，并提示重复值
        val startString = "Duplicate entry '"

        val startIndex = it.indexOf(startString) + startString.length
        val endIndex = it.indexOf("'", startIndex)
        var value = it.substring(startIndex, endIndex)
        if (value.contains("-")) {
            value = value.substring(0, value.indexOf("-"))
        }

        val startUniqueKeyIndex = it.indexOf("'", endIndex + 1)
        val endUniqueKeyIndex = it.indexOf("'", startUniqueKeyIndex + 1)
        val uniqueKey = it.substring(startUniqueKeyIndex, endUniqueKeyIndex)
        val tip = "该对象存在重复字段，值为：${value}"

        logError("${tip}，违反了索引${uniqueKey}${getCommonErrorTrace(e)}")
        return fail(ReturnCode.UNIQUE_CONSTRAINT_ERROR, tip)
    }

    open fun handleMultiChangeException(e: Exception): RespVo<*> {
        logError("该对象之前已经被修改，更新失败${getCommonErrorTrace(e)}")
        return fail(ReturnCode.MULTI_CHANGE_ERROR, "该对象之前已经被修改，更新失败")
    }

    open fun handleSqlGrammarException(e: Exception): RespVo<*> {
//        logError("SQL语句异常", e)
        logError("SQL语句异常${getCommonErrorTrace(e)}")
        return fail(ReturnCode.SYSTEM_ERROR, "SQL语句异常")
    }

    private fun getCommonErrorTrace(e: Exception) = "\n错误信息：Caused by:${e::class.java.name}:${e.message}\n" +
            "${toSimplifyTrace(e)}"

    open fun handleNoResourceFoundException(e: Exception): RespVo<*> {
        logError("无法找到对应页面${getCommonErrorTrace(e)}")
        return fail(ReturnCode.API_NOT_FOUND, "该请求接口无法找到")
    }

    open fun handleMethodArgumentValidException(
        e: MethodArgumentNotValidException
    ): RespVo<*> {
        val bindingResult = e.bindingResult
        val fieldErrors = bindingResult.fieldErrors
        val stringBuilder = StringBuilder()
        val errorSize = fieldErrors.size
        stringBuilder.append("共")
            .append(errorSize)
            .append("项错误：")
        for (fieldError in fieldErrors) {
            val defaultMessage = fieldError.defaultMessage
            stringBuilder.append(defaultMessage).append(",")
        }
        val resultString = stringBuilder.substring(0, stringBuilder.length - 1)
        logError("入参校验出错：${resultString}${getCommonErrorTrace(e)}")
        return fail(ReturnCode.VALIDATE_ERROR, resultString)
    }

    open fun handleCommonException(
        e: CommonException,
        response: HttpServletResponse
    ): RespVo<*> {
        val stackTraceString = toSimplifyTrace(e)
        logError("业务错误，错误码：${e.code}，错误信息：${e.info}，${getCommonErrorTrace(e)}")
        if (e.code == ReturnCode.UNAUTHORIZED) {
            response.status = ReturnCode.UNAUTHORIZED
        }

        return fail(e.code, e.info, e.data)
    }

    private fun toSimplifyTrace(e: Exception): String {
        val stackTraceString = e.stackTrace.map {
            "\t" + it.toString()
        }.filter {
            it.trim().startsWith(onflowConfig.projectPackageName) && !it.endsWith("(<generated>)")
        }.joinToString("\n")
        return stackTraceString
    }

    open fun fail(code: Int, info: String?, data: Any? = null): RespVo<*> {
        val respVo: RespVo<*> = RespVo<Any?>(code, data, info)
        val context = ContextHolder.getContext<Any, BaseLoginDTO<Any>>()
        if (context != null) {
            failHandleContext(context, respVo)

            ContextHolder.removeContext()
        }

        return respVo
    }

    open fun failHandleContext(context: BaseContext<*, *>, respVo: RespVo<*>) {
        val finishTime = System.currentTimeMillis()
        context.finishTime = finishTime
        val totalUseTime = finishTime - context.receiveTime
        context.totalUseTime = totalUseTime
        context.respVo = respVo
        if (context.apiParamDTO.important) {
            context.responseBody = respVo.toJSONString()
        }
    }
}